﻿#include "Shader.h"

#include <fstream>
#include <sstream>
#include <glm/gtc/type_ptr.hpp>

Shader::Shader(const std::string &path)
    : Asset(path)
{
  LoadFromFile(path + ".vert", path + ".frag");
}

Shader::~Shader()
{
  glDeleteProgram(m_programID);
}

void Shader::LoadFromFile(const std::string &vertPath, const std::string &fragPath)
{
  LOG_INFO("Loading Shaders: \n  -vert: {}\n  -frag:{}", vertPath, fragPath);

  std::string absPathVert = (std::filesystem::current_path() / vertPath).generic_string();
  std::string absPathFrag = (std::filesystem::current_path() / fragPath).generic_string();

  std::ifstream vertFile(absPathVert);
  std::stringstream vertStream;
  vertStream << vertFile.rdbuf();
  GLuint vertShader = CompileShader(GL_VERTEX_SHADER, vertStream.str());
  std::ifstream fragFile(absPathFrag);
  std::stringstream fragStream;
  fragStream << fragFile.rdbuf();
  GLuint fragShader = CompileShader(GL_FRAGMENT_SHADER, fragStream.str());
  m_programID = glCreateProgram();
  glAttachShader(m_programID, vertShader);
  glAttachShader(m_programID, fragShader);
  glLinkProgram(m_programID);

  GLint success;
  glGetProgramiv(m_programID, GL_LINK_STATUS, &success);
  if (!success)
  {
    char infoLog[512];
    glGetProgramInfoLog(m_programID, 512, NULL, infoLog);
    LOG_ERROR("Failed to Link Shader: {}", infoLog);
  }

  glDeleteShader(vertShader);
  glDeleteShader(fragShader);
}

GLuint Shader::CompileShader(GLenum type, const std::string &source)
{
  GLuint shader = glCreateShader(type);
  const char *src = source.c_str();

  glShaderSource(shader, 1, &src, nullptr);
  glCompileShader(shader);

  GLint success;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
  if (!success)
  {
    char infoLog[512];
    glGetShaderInfoLog(shader, 512, nullptr, infoLog);
    LOG_ERROR("Failed to Compile {} Shader: {}", (type == GL_VERTEX_SHADER ? "Vertex" : "Fragment"), infoLog);
  }
  return shader;
}

void Shader::Bind() const
{
  glUseProgram(m_programID);
}

void Shader::Unbind() const
{
  glUseProgram(0);
}

int32_t Shader::GetUniformLocation(const std::string &location) const
{
  return glGetUniformLocation(m_programID, location.c_str());
}

void Shader::SetInt(const std::string &name, int value) const
{
  glProgramUniform1i(m_programID, GetUniformLocation(name), value);
}

void Shader::SetUInt(const std::string &name, uint32_t value) const
{
  glProgramUniform1ui(m_programID, GetUniformLocation(name), value);
}

void Shader::SetFloat(const std::string &name, float value) const
{
  glProgramUniform1f(m_programID, GetUniformLocation(name), value);
}

void Shader::SetMat4(const std::string &name, const glm::mat4 &matrix) const
{
  glProgramUniformMatrix4fv(m_programID, GetUniformLocation(name), 1, GL_FALSE, glm::value_ptr(matrix));
}

void Shader::SetVec3(const std::string &name, const glm::vec3 &vec) const
{
  glProgramUniform3fv(m_programID, GetUniformLocation(name), 1, glm::value_ptr(vec));
}

void Shader::SetVec4(const std::string &name, const glm::vec4 &vec) const
{
  glProgramUniform4fv(m_programID, GetUniformLocation(name), 1, glm::value_ptr(vec));
}

void Shader::SetTexture(const std::string &name, const Ref<Texture> &texture, int slot) const
{
  texture->BindToSlot(slot);
  glProgramUniform1i(m_programID, GetUniformLocation(name), slot);
}
